/*
 * Copyright (c) 2005
 * XDoclet Team
 * All rights reserved.
 */
package org.xdoclet.plugin.qtags;

import java.lang.reflect.InvocationTargetException;

import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;

import org.generama.MetadataProvider;
import org.generama.QDoxCapableMetadataProvider;

import com.thoughtworks.qdox.model.DocletTag;
import com.thoughtworks.qdox.model.JavaClass;
import com.thoughtworks.qdox.model.JavaMethod;
import com.thoughtworks.qdox.model.Type;

/**
 * @author Aslak Helles&oslash;y
 * @author Konstantin Pribluda
 * @author Diogo Quintela
 * @version $Revision: 504 $
 */
public class QTagUtils {
    protected static Map acceptedTypes;
    protected static Map tokensByName;

    static {
        acceptedTypes = new HashMap();
        acceptedTypes.put("java.lang.Boolean", "java.lang.Boolean");
        acceptedTypes.put("boolean", "boolean");
        acceptedTypes.put("java.lang.Integer", "java.lang.Integer");
        acceptedTypes.put("int", "int");
        acceptedTypes.put("java.lang.String", "java.lang.String");
        tokensByName = new HashMap();
        tokensByName.put("", ",");
        tokensByName.put("comma", ",");
        tokensByName.put("semicolon", ";");
    }

    public String getDocletTagName(JavaClass clazz) {
        String name = clazz.getName();

        if (name.endsWith("DocletTag")) {
            name = name.substring(0, name.length() - "DocletTag".length());
        } else if (name.endsWith("Tag")) {
            name = name.substring(0, name.length() - "Tag".length());
        }

        return hyphenate(name, true);
    }

    public String getDocletTagParameterName(JavaMethod method) {
        String result = null;
        if (method.getTagByName("qtags.verbatim") == null) {
            result = hyphenate(method.getPropertyName(), false);
        } else {
            result = method.getPropertyName();
        }

        if (method.getName().equals("getName") || method.getName().equals("getValue")) {
            throw new RuntimeException("In order to declare a tag parameter named '" + result +
                "', declare a method called " + method.getName() + "_() instead of " + method.getName() +
                "(). This is to avoid clash with the method " + method.getName() + "() in " +
                DocletTag.class.getName());
        }

        return result;
    }

    public String isParameterRequired(JavaMethod method) {
        if (method.getTagByName("qtags.required") == null) {
            return "false";
        } else {
            return "true";
        }
    }

    public boolean allowedOn(JavaClass clazz, String location) {
        return !disallowedOn(clazz, location);
    }

    public boolean disallowedOn(JavaClass clazz, String location) {
        DocletTag[] tags = clazz.getTagsByName("qtags.location");

        if (tags.length == 0) {
            return false;
        }

        for (int i = 0; i < tags.length; i++) {
            if (location.equals(tags[i].getValue())) {
                return false;
            }
        }

        return true;
    }

    /**
     * register own tag library
     */
    public void registerLibrary(QDoxCapableMetadataProvider metadataProvider) {
        // try to register our tag library, not for us, but for users...
        // if available
        try {
            Class clazz = getClass().getClassLoader().loadClass("org.xdoclet.plugin.qtags.qtags.TagLibrary");

            if (clazz != null) {
                clazz.getDeclaredConstructor(new Class[] {MetadataProvider.class}).newInstance(new Object[] {metadataProvider});
            }
        } catch (ClassNotFoundException cnfe) {
            // we just do not care - shit can happen...
        } catch (InstantiationException ie) {
            // we just do not care - shit can happen...
        } catch (IllegalAccessException ie) {
            // we just do not care - shit can happen...
        } catch (NoSuchMethodException ie) {
            // we just do not care - shit can happen...
        } catch (InvocationTargetException ie) {
            // we just do not care - shit can happen...
        }
    }

    /**
     * whether generation is desired for this class.
     * we generate only if class ( interface ) is it is not ignored
     */
    public boolean shouldGenerate(Object metadata) {
        JavaClass javaClass = (JavaClass) metadata;
        boolean isTagClass = javaClass.isA(DocletTag.class.getName());
        boolean enabled = javaClass.getTagByName("qtags.ignore") == null;
        return javaClass.isInterface() && isTagClass && enabled;
    }

    private String hyphenate(String camelCase, boolean firstIsDot) {
        StringBuffer sb = new StringBuffer();
        sb.append(Character.toLowerCase(camelCase.charAt(0)));
        char[] chars = camelCase.toCharArray();
        char separator = firstIsDot ? '.' : '-';

        for (int i = 1; i < chars.length; i++) {
            char currentChar = chars[i];

            if (Character.isUpperCase(currentChar)) {
                sb.append(separator).append(Character.toLowerCase(currentChar));
                separator = '-';
            } else {
                sb.append(currentChar);
            }
        }

        String hyphenated = sb.toString();

        if (hyphenated.endsWith("_")) {
            hyphenated = hyphenated.substring(0, hyphenated.length() - 1);
        }

        return hyphenated;
    }

    public String getTokenizer(JavaMethod method) {
        String retVal = null;
        DocletTag tokenTag = method.getTagByName("qtags.list-token");

        if ((tokenTag != null) && (tokenTag.getValue() != null)) {
            retVal = (String) tokensByName.get(tokenTag.getValue());
        }

        if (retVal == null) {
            retVal = (String) tokensByName.get("");
        }

        return retVal;
    }

    public Collection getMethods(final JavaClass javaClass) {
        return CollectionUtils.select(Arrays.asList(javaClass.getMethods(true)),
            new Predicate() {
                public boolean evaluate(Object object) {
                    JavaMethod method = (JavaMethod) object;
                    Type returnType = method.getReturns();
                    String parent = method.getParentClass().getFullyQualifiedName();
                    boolean retVal = method.isPropertyAccessor();
                    retVal = retVal && !parent.equals(Object.class.getName());
                    retVal = retVal && !parent.equals(DocletTag.class.getName());
                    retVal = retVal && acceptedTypes.containsKey(returnType.getValue());
                    // Let's check the dimensions
                    // zero or one dimension is allowed
                    retVal = retVal && (returnType.getDimensions() <= 1);
                    
                    return retVal;
                }
            });
    }
}
